// Copyright (c) 2010 Thomas Stuefe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#pragma managed(push, off)
#include <windows.h>
#include <string.h>
#include "sqlite3.h"
#include "internals.h"
#pragma managed(pop)

#include <vcclr.h>

#include "IDatabase.h"
#include "StatementImpl.h"
#include "SqliteException.h"

using namespace System;

#define FAIL_IF_CLOSED \
	if (!_hdl) { \
		trcError(L"Statement already closed."); \
		throw gcnew SqliteException(_db, this, -1, NULL, L"Statement already closed."); \
	} 

namespace zencat {
namespace sqlite {


	// helper function, checks if the column number is valid, if not, an exception is thrown
	void StatementImpl::checkColumnNumber(int n) {

		// note: column numbers are 0 based

		if (_columnCount == 0) {
			trcError(L"Invalid column number %d. Statement 0x%p has no columns.", n, _hdl);
			throw gcnew ArgumentException(gcnew String("Invalid column number (statement has no columns)."));
		}

		if (n < 0 || n >= _columnCount) {
			trcError(L"Invalid column number %d. Valid column numbers for statement 0x%p are %d .. %d.", 
				n, _hdl, 0, _columnCount - 1);
			throw gcnew ArgumentException(gcnew String("Invalid column number."));
		}

	}

	// helper function, checks if the parameter number is valid, if not, an exception is throwm
	void StatementImpl::checkParameterNumber(int n) {
	
		// note: parameter numbers are 1 based
		
		if (_parameterCount == 0) {
			trcError(L"Invalid parameter number %d. Statement 0x%p has no parameters.", n, _hdl);
			throw gcnew ArgumentException(gcnew String("Invalid parameter number (statement has no parameters)."));
		}

		if (n < 1 || n > _parameterCount) {
			trcError(L"Invalid parameter number %d. Valid parameter numbers for statement 0x%p are %d .. %d.", 
				n, _hdl, 1, _parameterCount);
			throw gcnew ArgumentException(gcnew String("Invalid parameter number."));
		}

	}

	StatementImpl::StatementImpl(DatabaseImpl^ db, ::sqlite3_stmt* hdl, String^ sql) 
		:	_db(db)
		,	_dbHdl(NULL)
		,	_hdl(hdl)
		,	_sql(sql)
		,	_parameterCount(0)
		,	_columnCount(0)
		,	_columnNames(nullptr)
		,	_hasData(false)
		,	_isSelectStatement(false)
	{
		asrt(_db != nullptr);
		asrt(_hdl);
		asrt(sql != nullptr);

		// I will need (over and over again) the db handle for error reporting, so just keep it
		_dbHdl = _db->GetNativeHandle();
		asrt(_dbHdl);

		// prefetch parameter count
		_parameterCount = ::sqlite3_bind_parameter_count(hdl);

		// prefetch column count...
		_columnCount = ::sqlite3_column_count(hdl);
		
		// ... and column names (Note that it makes no sense to prefetch column types as well;
		// sqlite has no rigid typing, so types will differ from row to row
		_columnNames = gcnew array<String^> (_columnCount);
		for (int column = 0; column < _columnCount; column ++) {
			const wchar_t* const txt = (const wchar_t*) ::sqlite3_column_name16(_hdl, column);
			if (!txt) {
				const int errcode = ::sqlite3_extended_errcode(_dbHdl);
				const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
				trcSqliteError(errcode, errmsg, L"Statement 0x%p: failed to get column name for column %d.", _hdl, column);
				throw gcnew SqliteException(_db, this, errcode, errmsg, L"Failed to get column name.");
			}
			_columnNames[column] = gcnew String(txt);
		}

		// tell apart whether or not this is a select statement - if it is, tracing will look different
		if (sql->Trim()->StartsWith(L"SELECT", StringComparison::OrdinalIgnoreCase)) {
			_isSelectStatement = true;
		}

	}

	StatementImpl::~StatementImpl() {
		if (_hdl) {
			this->Close();
		}
	}

	StatementImpl::!StatementImpl() {
		if (_hdl) {
			this->Close();
		}
	}


	void StatementImpl::Close() {
		
		FAIL_IF_CLOSED;

		trcVerbose(L"Finalizing statement 0x%p...", _hdl);

		const int rc = ::sqlite3_finalize(_hdl);

		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(rc, errmsg, L"Error while finalizing statement 0x%p.", _hdl);
			SqliteException^ exc = gcnew SqliteException(_db, this, errcode, errmsg, L"Error while finalizing statement.");
			throw exc;
		}

		trcInfo(L"Statement 0x%p finalized.", _hdl);

		_db = nullptr;
		_hdl = NULL;
		_sql = nullptr;
		_hasData = false;
		_parameterCount = 0;
		_columnCount = 0;
		_columnNames = nullptr;

	}

	String^ StatementImpl::GetColumnName(int columnNumber) {
		FAIL_IF_CLOSED;
		checkColumnNumber(columnNumber);
		asrt(_columnNames);
		asrt(_columnNames->Length == _columnCount);
		return _columnNames[columnNumber];
	}

	array<String^>^ StatementImpl::ColumnNames::get() {
		FAIL_IF_CLOSED;
		asrt(_columnNames);
		asrt(_columnNames->Length == _columnCount);
		return _columnNames;		
	}

	DataType StatementImpl::GetColumnType(int columnNumber) {
		FAIL_IF_CLOSED;
		checkColumnNumber(columnNumber);
		const int rawType = ::sqlite3_column_type(_hdl, columnNumber);
		DataType type = dataTypeFromRawType(rawType);
		return type;
	}

	array<DataType>^ StatementImpl::ColumnTypes::get() {
		FAIL_IF_CLOSED;
		array<DataType>^ arr = gcnew array<DataType>(_columnCount);
		for (int i = 0; i < _columnCount; i ++) {
			arr[i] = this->GetColumnType(i);
		}
		return arr;
	}

	void StatementImpl::Reset() {
	
		FAIL_IF_CLOSED;

		trcVerbose(L"statement 0x%p: resetting...", _hdl);

		const int rc = sqlite3_reset(_hdl);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg, L"Statement 0x%p: reset failed.", _hdl);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Resetting statement failed.");
		}

		trcInfo(L"Statement 0x%p was reset.", _hdl);

	}

	bool StatementImpl::Step() {
	
		FAIL_IF_CLOSED;

		trcVerbose(L"statement 0x%p: stepping...", _hdl);

		_hasData = false;
		const int rc = sqlite3_step(_hdl);
		switch (rc) {
			case SQLITE_DONE:
				break;
			case SQLITE_ROW:
				_hasData = true;
				break;
			default:
				const int errcode = ::sqlite3_extended_errcode(_dbHdl);
				const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
				trcSqliteError(errcode, errmsg, L"Statement 0x%p: error while executing.", _hdl);
				throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while executing statement.");
				break;
		}

		// if trace level >= Info, trace compact infos about the success of the step.
		if (g_traceLevel >= TRCLVL_VERBOSE) {
			if (_isSelectStatement) {
				if (_hasData) {
					// For queries, trace the returned row, but in a way as not to trigger internal
					// data conversion
					TrcBuffer buf;
					for (int col = 0; col < _columnCount; col ++) {
						const int type = ::sqlite3_column_type(_hdl, col);
						switch(type) {
							case SQLITE_INTEGER: {
								const __int64 v = ::sqlite3_column_int64(_hdl, col);
								buf.printf(L"%I64d", v); 
								}
								break;
							case SQLITE_FLOAT: {
								const double v = ::sqlite3_column_double(_hdl, col);
								buf.printf(L"%I64f", v); 
								}
								break;
							case SQLITE3_TEXT: {
								const void* const v = ::sqlite3_column_text16(_hdl, col);
								buf.printf(L"\"%s\"", v ? v : L"NULL?");
								}
								break;
							case SQLITE_BLOB: 
								buf.puts(L"(blob)");
								break;
							case SQLITE_NULL: 
								buf.puts(L"(null)");
								break;
							default:
								asrt(false);
						}
						if (col < _columnCount - 1) {
							buf.putc(L' ');
						}
					}
					trcInfo(L"Statement 0x%p: step, returned: %s.", _hdl, buf.ptr());
				} else {
					trcInfo(L"Statement 0x%p: step, returned no data.", _hdl);
				}
			} else {

				// For non-queries: trace out the number of changed rows and the last inserted row id
				trcInfo(L"Statement 0x%p: step, %u rows changed, last inserted row id is %I64d.", 
					_hdl, ::sqlite3_changes(_dbHdl), (__int64) ::sqlite3_last_insert_rowid(_dbHdl));

			}

		} // end: if trace level > 0
	
		return _hasData;

	}

	void StatementImpl::BindStringToParameter(String^ value, int index) {
		
		FAIL_IF_CLOSED;

		checkParameterNumber(index);

		if (value == nullptr) {
			BindNullToParameter(index);
			return;
		}		

		pin_ptr<const wchar_t> pinned_value = PtrToStringChars(value);
		const wchar_t* const value0 = _wcsdup(pinned_value);

		trcVerbose(L"Statement 0x%p: Binding text value \"%s\" to parameter %d ...", _hdl, value0, index);

		const int len = value->Length;

		const int rc = ::sqlite3_bind_text16(_hdl, index, value0, len * sizeof(wchar_t), SQLITE_TRANSIENT);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg, L"Statement 0x%p: Error while binding text value \"%s\" to parameter %d.", 
				_hdl, value0, index);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while binding text parameter.");
		}
		
		trcInfo(L"Statement 0x%p: Text value \"%s\" successfully bound to parameter %d.", _hdl, value0, index);

	}

	void StatementImpl::BindInt64ToParameter(Int64 value, int index) {
		
		FAIL_IF_CLOSED;

		checkParameterNumber(index);

		trcVerbose(L"Statement 0x%p: Binding integer value %I64d to parameter %d...", _hdl, value, index);

		const int rc = ::sqlite3_bind_int64(_hdl, index, value);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg, L"Statement 0x%p: Error while binding integer value %I64d to parameter %d.", 
				_hdl, value, index);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while binding integer parameter.");
		}
		
		trcInfo(L"Statement 0x%p: Integer value %I64d successfully bound to parameter %d.", _hdl, value, index);

	}


	void StatementImpl::BindDoubleToParameter(Double value, int index) {
		
		FAIL_IF_CLOSED;

		checkParameterNumber(index);

		trcVerbose(L"Statement 0x%p: Binding double value %I64f to parameter %d ...", _hdl, value, index);

		const int rc = ::sqlite3_bind_double(_hdl, index, value);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg, L"Statement 0x%p: Error while binding double value %I64f to parameter %d.", 
				_hdl, value, index);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while binding double parameter.");
		}
		
		trcInfo(L"Statement 0x%p: double value %I64f bound to parameter %d.", _hdl, value, index);

	}

	void StatementImpl::BindNullToParameter(int index) {
	
		FAIL_IF_CLOSED;

		checkParameterNumber(index);

		trcVerbose(L"Statement 0x%p: Binding NULL to parameter %d ...", _hdl, index);

		const int rc = ::sqlite3_bind_null(_hdl, index);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg, L"Statement 0x%p: Error while binding NULL to parameter %d.", 
				_hdl, index);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while binding NULL.");
		}
		
		trcInfo(L"Statement 0x%p: parameter %d bound to NULL.", _hdl, index);
	
	}

	void StatementImpl::BindParameters(array<String^>^ values) {
		
		FAIL_IF_CLOSED;

		if (values == nullptr) {
			trcError(L"Statement 0x%p: Values are null");
			throw gcnew ApplicationException(gcnew String(L"Values Parameter is null"));
		}

		// Note: this only makes sense if parameters are numbered without gaps. If sql string contains ?NNN parameter
		// placeholders, calling this method might not make sense.
		if (values->Length != _parameterCount) {
			trcError(L"Statement 0x%p: Wrong number of parameters: %d (expected %d).", values->Length, _parameterCount);
			throw gcnew ApplicationException(gcnew String(L"Wrong number of parameters."));
		}
	
		// Now iterate thru the array, bind each string separately
		for(int i = 0; i < _parameterCount; i ++) {
			BindStringToParameter(values[i], i + 1); // Note: parameter numbering starts at 1
		}

	}

#define FAIL_IF_NO_DATA \
	if (!_hasData) { \
		trcError(L"Statement 0x%p: no data."); \
		throw gcnew SqliteException(_db, this, 0, NULL, L"Statement has no data"); \
	}

	String^ StatementImpl::ColumnValueString(int column) {
	
		FAIL_IF_CLOSED;
		FAIL_IF_NO_DATA;
		checkColumnNumber(column);

		trcVerbose(L"Statement 0x%p: retrieving string value for column %d ...", _hdl, column);

		const int internalType = ::sqlite3_column_type(_hdl, column);
		
		trcVerbose(L"Statement 0x%p: Column %d has internal type %d.", 
			_hdl, column, internalType);

		if (internalType == SQLITE_NULL) {
			trcInfo(L"Statement 0x%p: column %d contains NULL - returning null.", _hdl, column);
			return nullptr;
		} else if (internalType == SQLITE_BLOB) {
			trcInfo(L"Statement 0x%p: column %d contains Blob - ignored, returning null.", _hdl, column);
			return nullptr;
		}

		const wchar_t* const value = (const wchar_t*) ::sqlite3_column_text16(_hdl, column);
		if (!value) { // we handled NULL before, so this must be an error.
			const int errcode = ::sqlite3_extended_errcode(_dbHdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_dbHdl);
			trcSqliteError(errcode, errmsg,
				L"Statement 0x%p: Error while retrieving String value from column %d.", 
				_hdl, column);
			throw gcnew SqliteException(_db, this, errcode, errmsg, L"Error while retrieving String value.");
		}
		
		trcVerbose(L"Statement 0x%p: Column %d contains value \"%s\"", _hdl, column, value);

		return gcnew String(value);
	
	}


	Int64 StatementImpl::ColumnValueInt64(int column) {
	
		FAIL_IF_CLOSED;
		FAIL_IF_NO_DATA;
		checkColumnNumber(column);

		trcVerbose(L"Statement 0x%p: retrieving Int64 value for column %d ...", _hdl, column);

		const int internalType = ::sqlite3_column_type(_hdl, column);
		
		trcVerbose(L"Statement 0x%p: Column %d has internal type %d.", 
			_hdl, column, internalType);

		if (internalType == SQLITE_NULL) {
			trcInfo(L"Statement 0x%p: column %d contains NULL - returning 0L.", _hdl, column);
			return 0L;
		}

		const __int64 value = ::sqlite3_column_int64(_hdl, column);

		// No way to handle error here - sqlite3_column_int64 will return 0 in case of an error, which 
		// I cannot tell apart from a real 0. TODO - investigate
		
		trcVerbose(L"Statement 0x%p: Column %d contains value %I64d", _hdl, column, value);

		return value;	
	
	}

	Double StatementImpl::ColumnValueDouble(int column) {
	
		FAIL_IF_CLOSED;
		FAIL_IF_NO_DATA;
		checkColumnNumber(column);

		trcVerbose(L"Statement 0x%p: retrieving Double value for column %d ...", _hdl, column);

		const int internalType = ::sqlite3_column_type(_hdl, column);
		
		trcVerbose(L"Statement 0x%p: Column %d has internal type %d.", 
			_hdl, column, internalType);

		if (internalType == SQLITE_NULL) {
			trcInfo(L"Statement 0x%p: column %d contains NULL - returning 0.0.", _hdl, column);
			return 0.0;
		}

		const double value = ::sqlite3_column_double(_hdl, column);

		// No way to handle error here - sqlite3_column_int64 will return 0 in case of an error, which 
		// I cannot tell apart from a real 0. TODO - investigate
		
		trcVerbose(L"Statement 0x%p: Column %d contains value %I64f", _hdl, column, value);

		return value;	
	
	}

	bool StatementImpl::ColumnValueIsNULL(int column) {
	
		FAIL_IF_CLOSED;
		FAIL_IF_NO_DATA;
		checkColumnNumber(column);

		trcVerbose(L"Statement 0x%p: check column %d for NULL ...", _hdl, column);

		const int internalType = ::sqlite3_column_type(_hdl, column);
		
		trcVerbose(L"Statement 0x%p: Column %d has internal type %d.",
			_hdl, column, internalType);

		return internalType == SQLITE_NULL ? true : false;
		
	}

	array<String^>^ StatementImpl::ColumnValues() {

		FAIL_IF_CLOSED;
		FAIL_IF_NO_DATA;

		trcVerbose(L"Statement 0x%p: retrieving all values.");

		array<String^>^ arr = gcnew array<String^> (_columnCount);

		for (int i = 0; i < _columnCount; i ++) {
			arr[i] = ColumnValueString(i);
		}

		return arr;
	
	}


} // end namespace sqlite

} // end namespace zencat